﻿using FastColoredTextBoxNS;
using SPCoder.Config;
using SPCoder.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

namespace SPCoder.Windows
{
    public partial class Code : DockContent
    {
        public static readonly string pythonStyle = @"Resources\python.xml";

        public AutocompleteMenu popupMenu;

       
        public Code()
        {
            InitializeComponent();

            //create autocomplete popup menu
            popupMenu = new AutocompleteMenu(fctb);
            popupMenu.SearchPattern = @"[\w\.()(\w):=!<>]|(.)";
            popupMenu.AllowTabKey = true;
            //assign DynamicCollection as items source
            popupMenu.Items.SetAutocompleteItems(new DynamicCollection(popupMenu, fctb));
            
            //popupMenu.Items.MaximumSize = new System.Drawing.Size(300, 400);
            //popupMenu.Items.Width = 300;
            popupMenu.Items.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
            
            
            //fctb.OnTextChangedDelayed(fctb.Range);

            fctb.DescriptionFile = pythonStyle;
            fctb.Language = Language.Custom;
            //fctb.OnTextChangedDelayed(fctb.Range);
            fctb.CurrentLineColor = Color.FromArgb(200, 200, 255);

            fctb.CustomAction += fctb_CustomAction;
            
            fctb.HotkeysMapping.Add(Keys.F5, FCTBAction.CustomAction1);
            fctb.HotkeysMapping.Add(Keys.Control | Keys.S, FCTBAction.CustomAction2);
            fctb.HotkeysMapping.Add(Keys.Control | Keys.W, FCTBAction.CustomAction3);
            //fctb.AutoIndentNeeded += new EventHandler<AutoIndentEventArgs>(fctb_AutoIndentNeeded);
        }

        void fctb_CustomAction(object sender, CustomActionEventArgs e)
        {
            if (e.Action == FCTBAction.CustomAction1)
            {
                this.ExecuteSelection();
            } 
            else if (e.Action == FCTBAction.CustomAction2)
                {
                    this.SaveCurrentCode();    
                }
            else if (e.Action == FCTBAction.CustomAction3)
            {
                this.CloseCodeWindow();
            }
        }

        public string Source
        {
            get
            {
                return fctb.Text;
            }
            set
            {
                fctb.Text = value;
                //remove '*' from the end of the title
                this.Text = this.Text.TrimEnd('*');
            }

        }

        public string Title
        {
            get
            {
                return this.Text;
            }
            set
            {
                this.Text = value;
            }

        }
        public string FullFileName
        {
            get
            {
                return fctb.Tag as string;
            }
            set
            {
                fctb.Tag = value;
            }
        }

        public string FileName { get; set; }

        private void fctb_TextChangedDelayed(object sender, TextChangedEventArgs e)
        {
            //delete all markers
            fctb.Range.ClearFoldingMarkers();
            
            var currentIndent = 0;
            var lastNonEmptyLine = 0;

            for (int i = 0; i < fctb.LinesCount; i++)
            {
                var line = fctb[i];
                var spacesCount = line.StartSpacesCount;
                if (spacesCount == line.Count) //empty line
                    continue;

                if (currentIndent < spacesCount)
                    //append start folding marker
                    fctb[lastNonEmptyLine].FoldingStartMarker = "m" + currentIndent;
                else
                    if (currentIndent > spacesCount)
                        //append end folding marker
                        fctb[lastNonEmptyLine].FoldingEndMarker = "m" + spacesCount;

                currentIndent = spacesCount;
                lastNonEmptyLine = i;
            }
            HighlightInvisibleChars(e.ChangedRange);

            //
            if (!this.Text.EndsWith("*"))
            {
                this.Text += "*";
            }
        }

        private void fctb_AutoIndentNeeded(object sender, AutoIndentEventArgs e)
        {
            //we assign this handler for disable AutoIndent by folding
            if (e.PrevLineText.EndsWith(":"))
            {                
                e.Shift = e.TabLength;                                
            }
            
            //e.ShiftNextLines = e.TabLength;
        }

        Style invisibleCharsStyle = new InvisibleCharsRenderer(Pens.Gray);

        public void HighlightInvisibleChars()
        {
            Range range = fctb.Range;
            HighlightInvisibleChars(range);
        }

        public void HighlightInvisibleChars(Range range)
        {             
            range.ClearStyle(invisibleCharsStyle);
            //if (btInvisibleChars.Checked)
            if (SPCoderForm.MainForm.ShouldHighlightInvisibleChars())
                range.SetStyle(invisibleCharsStyle, @".$|.\r\n|\s");
        }

        public void ExecuteSelection()
        {
            try
            {
                SPCoderForm.MainForm.ClearOutput();
                string text;
                
                string selection = fctb.SelectedText;
                if (string.IsNullOrEmpty(selection))
                {
                    text = fctb.Text;
                }
                else
                {
                    text = selection;
                }
                object result = SPCoderForm.MainForm.ExecuteScript(text);

            }
            catch (Exception ex)
            {
                SPCoderForm.MainForm.LogException(ex);
            }
        }

        public void SaveCurrentCode()
        {
            SaveCurrentCode(true);
        }

        public bool SaveCurrentCode(bool forceOverwrite)
        {
            //Save a file            
            bool result = false;
            
            string text = fctb.Text;
            bool written = false;
            if (forceOverwrite)
            {
                if (fctb.Tag != null && !string.IsNullOrEmpty(fctb.Tag.ToString()))
                {
                    string tempFileName = fctb.Tag.ToString();
                    if (System.IO.File.Exists(tempFileName))
                    {
                        File.WriteAllText(tempFileName, text, Encoding.UTF8);
                        
                        SPCoderForm.MainForm.AppendToLog(string.Format("File '{0}' has been saved.", tempFileName));
                        written = true;                        
                        this.Text = Path.GetFileName(tempFileName);
                        result = true;
                    }
                }
                else
                {
                    forceOverwrite = false;
                }
            }

            if (!forceOverwrite)
            {
                if (!written)
                {
                    //else, open the dialog and save it
                    SaveFileDialog saveFileDialog = new SaveFileDialog();
                    saveFileDialog.Title = "Save script";
                    saveFileDialog.DefaultExt = "py";
                    saveFileDialog.InitialDirectory = ConfigUtils.FullDirectoryPath;
                    saveFileDialog.Filter = "IronPython files|*.py|All files|*.*";

                    saveFileDialog.FileOk += new System.ComponentModel.CancelEventHandler(saveFileDialog_FileOk);
                    saveFileDialog.ShowDialog(this);
                    if (string.IsNullOrEmpty(saveFileDialog.FileName))
                    {
                        return false;
                    }

                    File.WriteAllText(saveFileDialog.FileName, text, Encoding.UTF8);
                    written = true;                    
                    this.Text = Path.GetFileName(saveFileDialog.FileName);
                    fctb.Tag = saveFileDialog.FileName;
                    SPCoderForm.MainForm.AppendToLog(string.Format("File '{0}' has been saved.", saveFileDialog.FileName));

                    result = true;
                }
            }
            return result;
        }

        private void saveFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            //SaveFileDialog dialog = (SaveFileDialog) sender;


        }

        private void Code_FormClosed(object sender, FormClosedEventArgs e)
        {
            SPCoderForm.MainForm.CodeFormClosed(this);
        }

        private void Code_FormClosing(object sender, FormClosingEventArgs e)
        {           
            if (this.Text.EndsWith("*"))
            {
                var result = MessageBox.Show(this, "Save " + this.Text.TrimEnd('*') + "?", "Save",
                    MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                if (result == DialogResult.Cancel)
                {
                    e.Cancel = true;
                    SPCoderForm.MainForm.AllowClose = false;
                    return;
                }
                else if (result == DialogResult.Yes)                
                {
                    SaveCurrentCode(true);                    
                }
            }
            SPCoderForm.MainForm.RemoveCodeWindow(this);
        }

        protected void CloseCodeWindow()
        {            
            this.Close();
        }       
    }

    internal class DynamicCollection : IEnumerable<AutocompleteItem>
    {
        private AutocompleteMenu menu;
        private FastColoredTextBox tb;

        string[] keywords = { "and", "self", "del", "from", "not", "while", "as", "elif", "global", "or", "with", "assert", "else", "if", "pass", "yield", "break", "except", "import", "print", "class", "exec", "in", "raise", "continue", "finally", "is", "return", "def", "for", "lambda", "try" };
        string[] methods = { "Equals()", "GetHashCode()", "GetType()", "ToString()" };
        string[] snippets = { "if(^):\n", "if(^):\n\nelse:\n", "for ^ in :\n", "while(^):\n" };
        string[] declarationSnippets = { 
               "class ^:\ndef __init__(self):\n", "class ^:\n",
               "def ^():\n    "
               };


        public DynamicCollection(AutocompleteMenu menu, FastColoredTextBox tb)
        {
            this.menu = menu;
            this.tb = tb;
        }

        public IEnumerator<AutocompleteItem> GetEnumerator()
        {
            foreach (var item in snippets)
                yield return new SnippetAutocompleteItem(item);
            foreach (var item in declarationSnippets)
                yield return new DeclarationSnippet(item);

            foreach (var item in keywords)
                yield return new AutocompleteItem(item);

            //IList<AutocompleteItem> items = new List<AutocompleteItem>();
            //get current fragment of the text
            var text = menu.Fragment.Text;
            if (text.Contains(' '))
            {
                text = text.Substring(text.LastIndexOf(' ')).Trim();
            }

            //extract class name (part before dot)
            var parts = text.Split('.');
            if (parts.Length < 2)
                yield break;
            var myVar = parts[parts.Length - 2];

            //Samo prvi se gleda da li je u context-u!!!
            if (parts.Length <= 2)
            {
                if (SPCoderForm.MainForm.MyContext.Scope.ContainsVariable(myVar))
                {
                    object variable = SPCoderForm.MainForm.MyContext.Scope.GetVariable(myVar);
                    IList<string> all = SPCoderUtils.GetPropertiesAndMethods(variable, myVar);

                    foreach (string propMeth in all)
                    {
                        yield return new MethodAutocompleteItem(propMeth);
                    }
                }
            }
            else
            {
                //Here try to find properties/methods from expression
                IList<string> all = SPCoderUtils.GetPropertiesAndMethods(parts);
                if (all.Count > 0)
                {
                    foreach (string propMeth in all)
                    {
                        yield return new MethodAutocompleteItem(propMeth);
                    }
                }
                else
                    foreach (var variable in SPCoderForm.MainForm.MyContext.GetContext.Keys)
                    {
                        yield return new MethodAutocompleteItem(variable);
                    }
            }

          

            //yield return new InsertSpaceSnippet();
            //yield return new InsertSpaceSnippet(@"^(\w+)([=<>!:]+)(\w+)$");
            //yield return new InsertEnterSnippet();
            
        }
        
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    /// <summary>
    /// This item appears when any part of snippet text is typed
    /// </summary>
    class DeclarationSnippet : SnippetAutocompleteItem
    {
        public DeclarationSnippet(string snippet)
            : base(snippet)
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            var pattern = Regex.Escape(fragmentText);
            if (Regex.IsMatch(Text, "\\b" + pattern, RegexOptions.IgnoreCase))
                return CompareResult.Visible;
            return CompareResult.Hidden;
        }
    }

    /// <summary>
    /// Divides numbers and words: "123AND456" -> "123 AND 456"
    /// Or "i=2" -> "i = 2"
    /// </summary>
    class InsertSpaceSnippet : AutocompleteItem
    {
        string pattern;

        public InsertSpaceSnippet(string pattern)
            : base("")
        {
            this.pattern = pattern;
        }

        public InsertSpaceSnippet()
            : this(@"^(\d+)([a-zA-Z_]+)(\d*)$")
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            if (Regex.IsMatch(fragmentText, pattern))
            {
                Text = InsertSpaces(fragmentText);
                if (Text != fragmentText)
                    return CompareResult.Visible;
            }
            return CompareResult.Hidden;
        }

        public string InsertSpaces(string fragment)
        {
            var m = Regex.Match(fragment, pattern);
            if (m == null)
                return fragment;
            if (m.Groups[1].Value == "" && m.Groups[3].Value == "")
                return fragment;
            return (m.Groups[1].Value + " " + m.Groups[2].Value + " " + m.Groups[3].Value).Trim();
        }

        public override string ToolTipTitle
        {
            get
            {
                return Text;
            }
        }
    }

    /// <summary>
    /// Inerts line break after '}'
    /// </summary>
    class InsertEnterSnippet : AutocompleteItem
    {
        Place enterPlace = Place.Empty;

        public InsertEnterSnippet()
            : base("[Line break]")
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            var r = Parent.Fragment.Clone();
            while (r.Start.iChar > 0)
            {
                if (r.CharBeforeStart == '}')
                {
                    enterPlace = r.Start;
                    return CompareResult.Visible;
                }

                r.GoLeftThroughFolded();
            }

            return CompareResult.Hidden;
        }

        public override string GetTextForReplace()
        {
            //extend range
            Range r = Parent.Fragment;
            Place end = r.End;
            r.Start = enterPlace;
            r.End = r.End;
            //insert line break
            return Environment.NewLine + r.Text;
        }

        public override void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e)
        {
            base.OnSelected(popupMenu, e);
            if (Parent.Fragment.tb.AutoIndent)
                Parent.Fragment.tb.DoAutoIndent();
        }

        public override string ToolTipTitle
        {
            get
            {
                return "Insert line break after '}'";
            }
        }
    }

    public class InvisibleCharsRenderer : Style
    {
        Pen pen;

        public InvisibleCharsRenderer(Pen pen)
        {
            this.pen = pen;
        }

        public override void Draw(Graphics gr, Point position, Range range)
        {
            var tb = range.tb;
            using (Brush brush = new SolidBrush(pen.Color))
                foreach (var place in range)
                {
                    switch (tb[place].c)
                    {
                        case ' ':
                            var point = tb.PlaceToPoint(place);
                            point.Offset(tb.CharWidth / 2, tb.CharHeight / 2);
                            gr.DrawLine(pen, point.X, point.Y, point.X + 1, point.Y);
                            break;
                    }

                    //if (tb[place.iLine].Count - 1 == place.iChar)
                    //{
                    //    var point = tb.PlaceToPoint(place);
                    //    point.Offset(tb.CharWidth, 0);
                    //    gr.DrawString("¶", tb.Font, brush, point);
                    //}
                }
        }
    }
}